﻿Imports System.Diagnostics
Imports System.Collections.Generic
Imports System.Text
Imports System.Net.Sockets

Class TelnetAsyncClient

    Enum Verbs
        WILL = 251
        WONT = 252
        [DO] = 253
        DONT = 254
        IAC = 255
    End Enum

    Enum StatusLogin
        eStatusLogin_None = 0
        eStatusLogin_Negotiation
        eStatusLogin_WaitForUser
        eStatusLogin_WaitForPassword
        eStatusLogin_LoginOK
    End Enum

    Enum Options
        SGA = 3
    End Enum

    Public Event OnReceivData(ByVal inData As String)

    Public strErrorMsg As String

    Private netAsyncClient As NetAsyncClient
    Private intStatusLogin As StatusLogin
    Private strText As System.Text.StringBuilder

    Private strHost As String
    Private intPort As Integer
    Private strUsername As String
    Private strPassword As String
    Private intTimeOut As Integer

    Private Function OnReceiveDataLocal(ByVal inBuffer() As Byte, ByVal inSizeBuffer As Integer) As Boolean
        Dim intNum0 As Integer = 0
        Dim i As Integer = 0

        For i = 0 To inSizeBuffer
            If inBuffer(i) = 0 Then
                intNum0 += 1
                inBuffer(i) = 10
            Else
                intNum0 = 0
            End If
            If intNum0 = 2 Then
                Exit For
            End If
        Next

        Dim strData As String = Encoding.ASCII.GetString(inBuffer, 0, i - 1)

        RaiseEvent OnReceivData(strData)

        Return True
    End Function

    Private Function OnReceiveDataForLogin(ByVal inBuffer() As Byte, ByVal inSizeBuffer As Integer) As Boolean
        Return ParseTelnet(inBuffer, inSizeBuffer)
    End Function

    Private Function ParseTelnet(ByVal inBuffer() As Byte, ByVal inSizeBuffer As Integer) As Boolean
        Dim input As Integer
        Dim inputverb As Integer
        Dim inputoption As Integer

        For i As Integer = 0 To inSizeBuffer - 1

            input = inBuffer(i)
            Select Case input
                Case -1
                    strErrorMsg = "End of stream 1"
                    Return False
                Case CInt(Verbs.IAC)
                    If i + 1 > inSizeBuffer - 1 Then
                        strErrorMsg = "End of stream 2"
                        Return False
                    End If

                    ' interpret as command
                    inputverb = inBuffer(i + 1)
                    If inputverb = -1 Then
                        strErrorMsg = "End of stream 3"
                        Return False
                    End If
                    Select Case inputverb
                        Case CInt(Verbs.IAC)
                            'literal IAC = 255 escaped, so append char 255 to string
                            strText.Append(inputverb)

                        Case CInt(Verbs.[DO]), CInt(Verbs.DONT), CInt(Verbs.WILL), CInt(Verbs.WONT)
                            If i + 2 > inSizeBuffer - 1 Then
                                strErrorMsg = "End of stream 4"
                                Return False
                            End If

                            ' reply to all commands with "WONT", unless it is SGA (suppres go ahead)
                            inputoption = inBuffer(i + 2)
                            If inputoption = -1 Then
                                strErrorMsg = "End of stream 5"
                                Return False
                            End If

                            Dim sendByte(2) As Byte

                            sendByte(0) = CByte(Verbs.IAC)
                            If inputoption = CInt(Options.SGA) Then
                                sendByte(1) = If(inputverb = CInt(Verbs.[DO]), CByte(Verbs.WILL), CByte(Verbs.[DO]))
                            Else
                                sendByte(1) = If(inputverb = CInt(Verbs.[DO]), CByte(Verbs.WONT), CByte(Verbs.DONT))
                            End If
                            sendByte(2) = CByte(inputoption)
                            netAsyncClient.Send(sendByte)

                    End Select

                Case Else
                    strText.Append(ChrW(input))
            End Select

        Next

        Return True
    End Function

    Public Sub New(ByVal inHost As String, ByVal inPort As Integer)
        strHost = inHost
        intPort = inPort
        netAsyncClient = New NetAsyncClient
        strText = New StringBuilder
        intStatusLogin = StatusLogin.eStatusLogin_None

        ' Connect to server
        AddHandler netAsyncClient.OnReceiveData, AddressOf OnReceiveDataForLogin
        If Not netAsyncClient.Connect(strHost, intPort) Then
            strErrorMsg = "Impossible to connect"
        End If
    End Sub

    Public Function IsConnected() As Boolean
        Return netAsyncClient.IsConnected
    End Function

    Public Function Login(ByVal inUsername As String, ByVal inPassword As String, ByVal inTimeOut As Integer, Optional ByVal inIsOutputOn As Boolean = False) As Boolean
        If Not netAsyncClient.IsConnected Then
            strErrorMsg = "Socket closed"
            Return False
        End If

        If Not strText.ToString.TrimEnd().EndsWith(":") Then
            strErrorMsg = "Impossible to get login prompt"
            Return False
        End If

        strUsername = inUsername
        strPassword = inPassword
        intTimeOut = inTimeOut
        intStatusLogin = StatusLogin.eStatusLogin_WaitForUser

        Return LoginSteps()

    End Function

    Private Function LoginSteps() As Boolean
        Select Case intStatusLogin
            Case StatusLogin.eStatusLogin_Negotiation

            Case StatusLogin.eStatusLogin_WaitForUser
                'MsgBox(strText.ToString)
                'If Not WriteLine(strUsername) Then
                '    strErrorMsg = "Impossible to write login"
                '    Return False
                'End If
            Case StatusLogin.eStatusLogin_WaitForPassword

            Case StatusLogin.eStatusLogin_LoginOK

            Case StatusLogin.eStatusLogin_None

            Case Else

        End Select
    End Function


    Public Function EndSession() As Boolean
        Return Write("exit" & vbLf)
    End Function

    Public Function WriteLine(ByVal inCmd As String) As Boolean
        Return Write(inCmd & vbLf)
    End Function

    Public Function Write(ByVal inCmd As String) As Boolean
        If Not netAsyncClient.IsConnected Then
            strErrorMsg = "Socket closed"
            Return False
        End If

        Dim buf As Byte() = System.Text.ASCIIEncoding.ASCII.GetBytes(inCmd.Replace(vbNullChar & "xFF", vbNullChar & "xFF" & vbNullChar & "xFF"))
        netAsyncClient.Send(buf)

        Return True
    End Function

    'Public Function WriteAndWait(ByVal inSendMsg As String, ByVal inStringWanted As String, ByVal inMaxTime As Integer, Optional ByVal inIsOutputOn As Boolean = True) As Integer
    '    If Not WriteLine(inSendMsg) Then
    '        strErrorMsg = "Impossible to send command"
    '        Return False
    '    End If
    '    Call Read("", inIsOutputOn)

    '    If Not WaitFor(inStringWanted, inMaxTime, inIsOutputOn) Then
    '        strErrorMsg = "Wait too long"
    '        Return False
    '    End If

    '    Return True
    'End Function

    'Private Function WaitFor(ByVal inStringWanted As String, ByVal inMaxTime As Integer, Optional ByVal inIsOutputOn As Boolean = True) As Boolean
    '    ' Get the starting time
    '    Dim lngStart As Long = DateTime.Now.AddSeconds(inMaxTime).Ticks
    '    Dim lngCurTime As Long = 0
    '    Dim strData As String = ""

    '    Read(strData, inIsOutputOn)
    '    While strData.ToLower().IndexOf(inStringWanted.ToLower()) = -1
    '        ' Timeout logic
    '        lngCurTime = DateTime.Now.Ticks
    '        If lngCurTime > lngStart Then
    '            Return False
    '        End If
    '        Application.DoEvents()
    '        Read(strData, inIsOutputOn)
    '    End While

    '    Return True
    'End Function

    'Private Function ReadForLogin(ByVal inTimeOut As Integer, ByRef outString As String) As Boolean
    '    If Not netAsyncClient.IsConnected Then
    '        strErrorMsg = "Socket closed"
    '        Return False
    '    End If

    '    Dim strValue As New StringBuilder()
    '    Dim firstTime As Date = Now

    '    Do
    '        Call ParseTelnet(strValue)
    '        Call System.Threading.Thread.Sleep(1)
    '    Loop While Now.Subtract(firstTime).TotalMilliseconds < inTimeOut

    '    outString = strValue.ToString
    '    Return True
    'End Function

    'Public Function Read(Optional ByRef outString As String = "", Optional ByVal inIsOutputOn As Boolean = True) As Boolean
    '    Try
    '        If Not netAsyncClient.IsConnected Then
    '            strErrorMsg = "Socket closed"
    '            Return False
    '        End If

    '        Dim bytes(tcpSocket.ReceiveBufferSize) As Byte

    '        tcpSocket.GetStream.Read(bytes, 0, CInt(tcpSocket.ReceiveBufferSize))

    '        Dim intNum0 As Integer = 0
    '        Dim i As Integer = 0
    '        For i = 0 To bytes.Length - 1
    '            If bytes(i) = 0 Then
    '                intNum0 += 1
    '                bytes(i) = 10
    '            Else
    '                intNum0 = 0
    '            End If
    '            If intNum0 = 2 Then
    '                Exit For
    '            End If
    '        Next

    '        Dim strData As String = Encoding.ASCII.GetString(bytes, 0, i - 1)

    '        outString = strData
    '        If inIsOutputOn Then RaiseEvent OnReceivData(strData)

    '        Return True

    '    Catch ex As Exception
    '        strErrorMsg = ex.Message
    '        Return False
    '    End Try
    'End Function

    'Private Sub ParseTelnet(ByRef sb As StringBuilder)
    '    Dim input As Integer
    '    Dim inputverb As Integer
    '    Dim inputoption As Integer

    '    While netAsyncClient.client.Available > 0

    '        input = netAsyncClient.client..GetStream().ReadByte()
    '        Select Case input
    '            Case -1
    '                MsgBox("end of stream")
    '            Case CInt(Verbs.IAC)
    '                ' interpret as command
    '                inputverb = tcpSocket.GetStream().ReadByte()

    '                If inputverb = -1 Then
    '                    MsgBox("end of stream 2")
    '                    Exit Select
    '                End If

    '                Select Case inputverb
    '                    Case CInt(Verbs.IAC)
    '                        'literal IAC = 255 escaped, so append char 255 to string
    '                        sb.Append(inputverb)

    '                    Case CInt(Verbs.[DO]), CInt(Verbs.DONT), CInt(Verbs.WILL), CInt(Verbs.WONT)
    '                        ' reply to all commands with "WONT", unless it is SGA (suppres go ahead)
    '                        inputoption = tcpSocket.GetStream().ReadByte()
    '                        If inputoption = -1 Then
    '                            MsgBox("end of stream 3")
    '                            Exit Select
    '                        End If

    '                        tcpSocket.GetStream().WriteByte(CByte(Verbs.IAC))
    '                        If inputoption = CInt(Options.SGA) Then
    '                            tcpSocket.GetStream().WriteByte(If(inputverb = CInt(Verbs.[DO]), CByte(Verbs.WILL), CByte(Verbs.[DO])))
    '                        Else
    '                            tcpSocket.GetStream().WriteByte(If(inputverb = CInt(Verbs.[DO]), CByte(Verbs.WONT), CByte(Verbs.DONT)))
    '                        End If
    '                        tcpSocket.GetStream().WriteByte(CByte(inputoption))

    '                End Select

    '            Case Else
    '                sb.Append(ChrW(input))
    '        End Select

    '    End While

    'End Sub

    Protected Overrides Sub Finalize()
        netAsyncClient = Nothing
        MyBase.Finalize()
    End Sub

End Class
